#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         GenericObject.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  GenericObject is the base (abstract) class for
#               all persistent KnowledgeEngine objects.
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from ClassInfo import ClassInfo
from PropertyReflection import *
from PropertyAccess import *
from PropertyDefinition import *
from PropertyTypes import *
from PropertyFormats import *
from PropertyStructures import *
from EventDefinition import *
from Core import getClassInfo, getPermittedChildren, getSecurityService, getUserProviderService, getCreditManagementService, getGroupProviderService, getAccessory, getRegisteredAccessories
from Permission import *
from SimpleWizard import *
from WizardState import *
from Products.KnowledgeEngine.Utils.TypeInfo import StubTypeInfo 
from Products.KnowledgeEngine.Assessment import Assessment

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from Globals import DTMLFile, ImageFile
from AccessControl import ClassSecurityInfo
from Acquisition import Implicit, aq_base, aq_inner, aq_parent
from Globals import Persistent
from AccessControl.Role import RoleManager
from App.Dialogs import MessageDialog
from OFS.SimpleItem import Item
from OFS.Traversable import Traversable
from OFS.CopySupport import CopyError
from RestrictedPython.Utilities import same_type

#####################################################################
## Zope CMF Library Imports
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import permissions
from Products.CMFCore.permissions import ModifyPortalContent

#####################################################################
## Plone Library Imports
from Products.CMFPlone.PloneFolder import PloneFolder
from Products.CMFCore.utils import getToolByName
 
#####################################################################
## Python Library Imports
import types

from xml.dom.minidom import getDOMImplementation

#####################################################################
## GenericObjectInfo Class Definition
class GenericObjectInfo(ClassInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'GenericObject'


    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Generic Object"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.

            This is a short string used for prefixing object identities
        """

        return "genericobject"

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            ABSTRACT: Return the appropriate icon file for the class.
            All concrete classes must provide an icon!
        """

        return None

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == "GenericObject" or ClassInfo.isInstanceOf(self, className)

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.

            Override an return true if the instance is to be
            a container for other instances.

            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        #by default objects aren't containers.
        #(if you want to make an object a container, override this method in your super class)
        return false

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #DEFAULT RULE: GenericObjects are permitted in GenericObjects that are containers
        return containerClassInfo.isContainer()

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #DEFAULT RULE: GenericObjects can only have children if they are containers
        return self.isContainer()

    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class properties for reflection
        ClassInfo.onConfigureProperties(self)

        #add the standard 'identity' property
        self.addPropertyDefinition(PropertyDefinition(identity="identity", 
                                                      displayName="Identity", 
                                                      description="The Knowledge Engine unique identity for this object", 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      constructorEVAP=evap_NONE,
                                                      propertyEditorEVAP=evap_READONLY, 
                                                      isMandatory=true))

        #add the standard 'id' property
        self.addPropertyDefinition(PropertyDefinition(identity="id", 
                                                      displayName="Short Name", 
                                                      defaultValue=self.getClassName().lower(), 
                                                      description="Short Name is part of the item's web address. For accessibility reasons, do not use spaces, upper case, underscores, or special characters.", 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      constructorEVAP=evap_READWRITE, 
                                                      propertyEditorEVAP=evap_NONE, 
                                                      isMandatory=true))

        #add the standard 'title' property
        self.addPropertyDefinition(PropertyDefinition(identity="title", 
                                                      displayName="Title", 
                                                      defaultValue=self.getDisplayName(), 
                                                      description="The full title to be used when displaying this object", 
                                                      propertyFormat=pf_TEXT_SINGLELINE))

        #add the 'description' property
        self.addPropertyDefinition(PropertyDefinition(identity="description", 
                                                      displayName="Description", 
                                                      description="The intended purpose of this component. Displayed to the user (not in the assessment editor)", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_HTML, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'developerNotes' property
        self.addPropertyDefinition(PropertyDefinition(identity="developerNotes", 
                                                      displayName="Developer Notes", 
                                                      description="Private notes about this component.  Only displayed here.", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_MULTILINE, 
                                                      propertyStructure=ps_ATOMIC))

    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """


        #initialise the super-class events for reflection
        ClassInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific permission definitions.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """


        #initialise the super-class permissions for reflection
        ClassInfo.onConfigurePermissions(self)

    #---------------------------------------------------------------#

#####################################################################
## GenericObject Class Definition
class GenericObject(PloneFolder, PropertyReflection, PropertyAccess):
    """
        ABSTRACT: Base class for all Faulkner Knowledge Engine components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = GenericObjectInfo()

    security = ClassSecurityInfo()

    _v_Model = None             #the model to which the generic object belongs

    #---------------------------------------------------------------#

    #ZOPE: The types allowable as child components
    all_meta_types = (
    )

    #---------------------------------------------------------------#

    # Define the default zope security information
    # (definitions can be extended, refined and overridden in subclasses)
    #TODO: security=ClassSecurityInfo()
    #TODO: security.declarePublic('index_html')

    #---------------------------------------------------------------#
    # Shared DTML Documents
    #propertyEditor = DTMLFile('dtml/propertyEditor', globals())
    #eventEditor = DTMLFile('dtml/eventEditor', globals())
    #epozEditor = DTMLFile('dtml/epozEditor', globals())
    #textEditor = DTMLFile('dtml/textEditor', globals())

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return self._CLASSINFO.getClassName()

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"

        self.id = id
        self.identity = identity

        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def __setstate__(self, state):
        """
            Initialise the state of the Object
            (does backward compatibility)
        """

        #perform default persistent actions
        Persistent.__setstate__(self, state)

        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """
        
        #nothing really to do here
        pass

    #---------------------------------------------------------------#

    def setModified(self):
        """
            Forces the object persistent state to be set as modified
        """

        self._p_changed = 1  #signal mutable object change for Zope

    #---------------------------------------------------------------#

    def clearManagementTabs(self):
        """
            Clear the management tabs for the object
        """

        self.manage_options = ()

    #---------------------------------------------------------------#

    def existsManagementTab(self, label):
        """
            Return if the specified management tab exists
        """

        result = false

        for tabDictionary in self.manage_options:
            result = result or (tabDictionary.has_key('label') and tabDictionary['label'] == label)

        return result

    #---------------------------------------------------------------#

    def addManagementTab(self, label, action, help = None, addToFront = false):
        """
            Add the specified management tab, replacing an existing
            one if necessary
        """

        #does the tab actually exist?
        if self.existsManagementTab(label):
            #already exists... so update it
            for tabDictionary in self.manage_options:

                if tabDictionary.has_key('label') and tabDictionary['label'] == label:
                    tabDictionary['action'] = action

                    if help <> None:
                        tabDictionary['help'] = help

        else:
            #new tab... build the new tab definition
            tabDictionary = {'label':label, 'action':action}
            if help <> None:
                tabDictionary['help'] = help

            if addToFront:
                self.manage_options = (tabDictionary,) + self.manage_options
            else:
                self.manage_options = self.manage_options + (tabDictionary,)

    #---------------------------------------------------------------#

    def getIdentityType(self):
        "Returns the Identity Type for the object"
        return self.identity_type

    #---------------------------------------------------------------#

    def getIdentity(self):
        "Returns the unique identity of the object"
        return self.identity

    #---------------------------------------------------------------#

    def getTitle(self):
        "Returns the Title of the object"
        return self.title

    #---------------------------------------------------------------#

    def setTitle(self, title):
        "Set the Title of the object"
        self.title = title

    #---------------------------------------------------------------#

    def getTitleOrId(self):
        "Returns the Title (if the object has one), otherwise returns the Zope Id"

        if len(self.getTitle()) > 0:
            return self.getTitle()
        else:
            return self.getId()

    #---------------------------------------------------------------#

    def hasTitle(self):
        "Returns if the object has a title"
        return len(self.getTitle()) > 0

    #---------------------------------------------------------------#

    def getDescription(self):
        """
            Returns the description of the object
        """
        
        return self.description

    #---------------------------------------------------------------#

    def setDescription(self, description):
        """
            Sets the description of the object
        """
        
        self.description = description

    #---------------------------------------------------------------#

    def hasDescription(self):
        """
            Returns if the object has a description
        """
        
        return len(self.getDescription()) > 0

    #---------------------------------------------------------------#

    def isPublishable(self):
        "Returns if the object is publishable"

        #TODO: check security here for current session
        return true

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Does this object support containing GenericObject based things
            (other than events)
        """

        return self.getClassInfo().isContainer()

    #---------------------------------------------------------------#

    def getParent(self):
        "Return the parent container that this object is within"

        #return the parent of the inner object (all outer wrappers removed)
        return self.aq_inner.aq_parent

    #---------------------------------------------------------------#

    def getModel(self):
        """
            Returns the Model in which this component is located.
        """

        #have we already worked out the model
        if self._v_Model == None:

            #get the parent of this component
            parent = self.getParent()

            #is the parent a Model?
            if parent.meta_type == 'Model':
                self._v_Model = parent

            else:
                self._v_Model = parent.getModel()

        #returned the cached model
        return self._v_Model

    #---------------------------------------------------------------#

    def getAbsolutePath(self):
        "Return the path to the object in the environment"

        #get that absolute path of the parent
        lPath = self.getParent().getAbsolutePath() + "/" + self.getId()
        return lPath

    #---------------------------------------------------------------#

    def getParentByInstanceOf(self, className):
        """
            Return the most immediate ancestor that implements
            the specified interface. Returns None if not found.
        """

        lParent = self.getParent()

        while hasattr(lParent, 'isInstanceOf') and not lParent.isInstanceOf(className):
            lParent = lParent.getParent()

        if hasattr(lParent, 'isInstanceOf') and lParent.isInstanceOf(className):
            return lParent
        else:
            return None

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the
            identity of this object is changed.

            Called By GenericObject.onAfterClone
        """

        pass

        ###print "DEBUG: GenericObject.onBeforeIdentityChange: Completed"

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the
            identity of this object has changed.

            The identityMappings dictionary holds
            mappings from oldIdentities -> newIdentities
            (including this object)

            Called By GenericObject.manage_afterClone
        """

        pass

        ###print "DEBUG: GenericObject.onAfterIdentityChange: Completed"

    #---------------------------------------------------------------#

    def alterIdentity(self):
        """
            Generates and changes the identity of the object
            CALLED BY: onAfterClone to ensure that pasted objects
            have different identities
        """

        print "Generating new identity for %s" % self.absolute_url()
        

        #generate a new identity for the object
        self.identity = generateIdentity(self.identity_type)

    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = None):
        "Handle Knowledge Engine specific actions after this object has been cloned"

        if identityMappings is None: identityMappings = {} 
        
        """
            Issue #813 - Copy and Paste causing KERL to be recalculated incorrectly 
            
            More recent versions of Zope and CMF appear to have a "Bug" (More of a lack of communication between product developers)
            that causes onAfterClone to be called multiple times.
            
            When an object is Pasted...
            
            1. manage_afterClone is called on each object as it's pasted. This is the old, expected, and sane behaviour.
            
            2. When pasting a folderish object, OFS.subscribers calls "callManageAfterAdd", then passes the buck along to "dispatchToSublocations", 
               which recursively calls manage_afterAdd on each subobject, even though it's already been called by Paste.
               
            3. When recataloguing, CMFCore.CMFCatalogAware then also calls manage_afterClone on each subobject. 
            
            This means that any given object will be called an increasing number of times depending on how many 
            levels of folders it is under.
            
            
            The solution: We need to set an attribute on the object marking it as already cloned. We don't want this to persist otherwise it'll
                          affect future copy-paste actions on that object, so we just want a volatile attribute.
                          
                          However, volatile attributes can still stick around across multiple requests (and there's still a possibility that
                          multiple copy-paste actions could be called by a script with a single request), so we need to get a unique hash for
                          each object and use that to mark as cloned.
        """
            
            
            
        # Dictionary used to mark objects as cloned
        if not hasattr(self, "_v_ke_cloned"): self._v_ke_cloned = {}
                
        # Generate our hash
        objectHash = hash(self) 
        
        if objectHash in self._v_ke_cloned:
            # Object has already been cloned, bailing
            return
        
        # Mark this object as already cloned
        self._v_ke_cloned[ objectHash ] = True
        
        # We can continue on as normal now, knowing that we won't re-re-calculate each object.

        #handle before identity change
        self.onBeforeIdentityChange()

        #remember the old identity
        oldIdentity = self.identity

        # Jamesd 20060627 Trac #630, #631 - Only generate a new identity if we havn't already
        if oldIdentity not in identityMappings:
            #generate a new identity for the object
            self.alterIdentity()

        #now re-index the object as it's identity has changed
        self.indexObject()

        #has the identity changed? (alterIdentity may not change the identity eg: like events don't)
        if oldIdentity != self.identity:
            #update the identityMappings with the old -> new identity
            identityMappings.update({oldIdentity:self.identity})
                  
            

        ###print "DEBUG: GenericObject.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this object has been added to a container
        """

        pass

        ###print "DEBUG: GenericObject.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        "Handle Knowledge Engine specific actions before this object is deleted"

        self.unindexObject()

        pass

        ###print "DEBUG: GenericObject.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def onAfterEdit(self):
        """
            Handle Knowledge Engine specific actions after this
            object has been edited
        """
        
        self.reindexObject()

        pass
    
    #---------------------------------------------------------------#
    
    def onBeforeEdit(self):
        """
            Handle Knowledge Engine specific actions before sthis
            object has been edited
        """
        
        pass    

    #---------------------------------------------------------------#

    def resolveIdentities(self, identityMappings = None):
        """
            Attempt to resolve all identity references
            for this object and its decendants using the
            provided identityMappings dictionary
        """
        
        if identityMappings is None: identityMappings = {}

        #handle changing identities for this object
        self.onAfterIdentityChange(identityMappings)

        #resolve decendants
        if self.isContainer():
            for object in self.objectValues():
                if isinstance(object, GenericObject):
                    object.resolveIdentities(identityMappings)

    #---------------------------------------------------------------#

    def manage_afterClone(self, item):
        "FINAL: Override the standard Zope afterClone management"

        #do the usual Zope processing
        PloneFolder.manage_afterClone(self, item)

        #print "manage_afterClone"

        #does the item (root of the clone) have an identities mapping?
        if not hasattr(item, "_identityMappings"):
            item._identityMappings = {}

        #print "Getting old identity mappings: %s" % (item._identityMappings)

        #perform the clone operations
        self.onAfterClone(item._identityMappings)

        #resolve the old identities from the root of the clone (if this is the root) down
        if self == item:
            self.resolveIdentities(item._identityMappings)

            #tidy up (if this is the last call)
            if self == item:
                #remove the temporary identity mappings attribute
                delattr(item, "_identityMappings")

                #as it is a clone, we need to adjust the position of the component
                #so it is last in the container
                self.setAsLast()

    #---------------------------------------------------------------#

    def manage_afterAdd(self, item, container):
        "FINAL: Override the standard Zope afterAdd management"

        #do the usual Zope processing
        PloneFolder.manage_afterAdd(self, item, container)

        #handle the knowledge engine specific updates
        self.onAfterAdd()

    #---------------------------------------------------------------#

    def manage_beforeDelete(self, item, container):
        """
            FINAL: Override the standard Zope beforeDelete management
        """
        print "KE: manage_beforeDelete"
        ###print "DEBUG: GenericObject.manage_beforeDelete: Commenced"

        #handle the knowledge engine specific updates
        self.onBeforeDelete()


        if self.isContainer():
            for object in self.getChildren():
                object.manage_beforeDelete(object, container=self)

                                    

        #do the usual Zope processing
        PloneFolder.manage_beforeDelete(self, item, container)

        ###print "DEBUG: GenericObject.manage_beforeDelete: Completed"

    #---------------------------------------------------------------#

    def getClassInfo(self):
        """
            FINAL: All decendants of this class are expected to
            provide a ClassInfo instance
        """

        return self._CLASSINFO

    #---------------------------------------------------------------#

    def getClassFamilyName(self):
        """
            Return the name of the family to which the the
            class (and subclasses) and it's subclasses belong/
            
            This is used to enable searching for families of 
            generic objects, eg: 'EntitySource' instances.
            
            By default the family name is the same as the class name
        """
        
        return self.getClassInfo().getClassFamilyName() 

    #---------------------------------------------------------------#

    def isLockedForEditing(self):
        """
            Return if the object is locked for editing
        """
        
        #is this a model?  
        if self.isInstanceOf('Model'):
            return self.getModel().isLockedForEditing()
        
        #portal objects can't be locked for editing
        elif self.getClassInfo().isPortalObject():
            return false
     
        #otherwise it's up to the containing model
        else:
            #this object is locked if it's model is locked for editing
            return self.getModel().isLockedForEditing()

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            CONCRETE: Return if the object is a sub-class of
            the specified class name.

            NOTE: we do this to avoid Zope instanceof problems,
            including resolving cyclic references/imports
        """

        return self.getClassInfo().isInstanceOf(className)

    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the component is allowed to be previewed.

            By default all components can be previewed, some however
            should not be previewed. eg: Options whose display is
            dependant on their parents.
        """

        return false

    #---------------------------------------------------------------#

    def getNrProperties(self):
        """
            Return the number of properties defined
            by the class implementing this interface
        """

        return self.getClassInfo().getNrProperties()

    #---------------------------------------------------------------#

    def isPropertyDefined(self, propertyIdentity):
        """
            Return if the specified property is defined
            by the class implementing this interface.
        """

        return self.getClassInfo().isPropertyDefined(propertyIdentity)

    #---------------------------------------------------------------#

    def getPropertyDefinition(self, propertyIdentity):
        """
            Return the PropertyDefinition for the specified
            property identity. Returns None if the proeprty definition
            is unknown

            RETURN TYPE: PropertyDefinition
        """

        return self.getClassInfo().getPropertyDefinition(propertyIdentity)

    #---------------------------------------------------------------#

    def getPropertyDefinitions(self):
        """
            Return the PropertyDefinitions for the
            class implementing this interface

            RETURN TYPE: [PropertyDefinition]
        """

        return self.getClassInfo().getPropertyDefinitions()

    #---------------------------------------------------------------#

    def addPropertyDefinition(self, propertyDefinition):
        """
            Adds/overrides the specified property definition to/in
            the class implementing this interface
        """

        self.getClassInfo().addPropertyDefinition(propertyDefinition)

    #---------------------------------------------------------------#

    def removePropertyDefinition(self, propertyIdentity):
        """
            Removes the specified property definition
            from the class implementing this interface
        """

        self.getClassInfo().removePropertyDefinition(propertyIdentity)

    #---------------------------------------------------------------#

    def hasEditableConstructorProperties(self):
        """
            Returns if the class implementing this
            interface has editable constructor properties
        """

        return self.getClassInfo().hasEditableConstructorProperties()

    #---------------------------------------------------------------#

    def hasEditableProperties(self):
        """
            Returns if the class implementing this
            interface has editable properties
        """

        return self.getClassInfo().hasEditableProperties()

    #---------------------------------------------------------------#

    def hasDefaultView(self):
        """
            PLONE Support:

            Returns if the object has a default view.  By setting this
            to true, a view tab will be added to the object.

            If this is false, attempting to visit the / (default view)
            for a component will redirect the client to the
            next available parent that has a default view.

            (Worst case, this is the Model component)
        """

        return false

    #---------------------------------------------------------------#

    def getZopeMetaTypes(self):
        """
            ABSTRACT: Returns specialized Zope all_meta_types tuple
            for the implementing class.

            NOTE: override this method to provide specialized
            all_meta_types tuple the implementing class

            NOTE: You should probably include the super class result
            of this method when overriding.
        """

        #the default is no specialized Meta Types.
        return ()

    #---------------------------------------------------------------#

    def setAsLast(self):
        """
            Set this component to be the last in the container
            iff it is orderable and the container is ordered
        """

        #get the parent
        parent = self.getParent()

        #set the position of this component in the container (if ordered)
        if self.isOrderable() and parent.isOrderedContainer():

            #get an ordered list of siblings (without this sibling)
            orderedSiblings = [sibling for sibling in parent.getChildrenByOrder() if sibling != self]

            #set the order of the current siblings
            nextOrder = 1.0
            for sibling in orderedSiblings:
                sibling.setOrder(nextOrder)
                nextOrder = nextOrder + 1.0

            #now set the current to be the last
            self.setOrder(nextOrder)

    #---------------------------------------------------------------#

    def getOrder(self):
        """
            Return the relative position of the object in
            it's container.  Return None if the object can't be ordered.

            PRE: self.isOrderable()
        """

        if self.isOrderable():
            return self.order
        else:
            return None

    #---------------------------------------------------------------#

    def setOrder(self, order):
        """
            Sets the Order of the component in it's container

            PRE: self.isOrderable()
        """

        if self.isOrderable():
            self.order = order

    #---------------------------------------------------------------#

    def isOrderable(self):
        """
            Return if the object can be ordered in an ordered
            container.  Typically objects with 'order' attributes
            between -1000 and 1000 can be ordered.
        """

        return hasattr(self, 'order') and self.order > -1000 and self.order < 1000

    #---------------------------------------------------------------#

    def getDescendants(self, lResult = None):
        """
            Returns the descendants of the container as a
            dictionary, indexed by GenericObject.getIdentity()
        """
        
        if lResult is None: lResult = {}   

        #is it a container?
        if self.isContainer():
            for lObject in self.objectValues():

                #is the object derived from GenericObject?
                if isinstance(lObject, GenericObject):

                    #add the current object to the result
                    lResult[lObject.getIdentity()] = lObject

                    #is the current object composite?
                    if lObject.isContainer():

                        #add the object descendants
                        lResult.update(lObject.getDescendants())

            return lResult

        else:
            return lResult

    #---------------------------------------------------------------#

    def isChildOf(self, parentObject):
        """
            Returns a BOOLEAN depending on whether the component
            is a child of the given parentObject
        """
        
        return self.getIdentity() in [component.getIdentity() for component in parentObject.getDescendants()]

    #---------------------------------------------------------------#
    
    def getIsDisabled(self):
        """ By default, GeneribObjects are never disabled """
        
        return False
    
    #---------------------------------------------------------------#

    def getChildren(self, includeDisabled=False):
        """
            Returns a sequence of the immediate child objects
            (that are not Events) but any other zope object is permitted.
        """

        #no children to start with!
        children = []

        #is it a container? (get all of the non-event children)
        if self.isContainer():
            for child in self.objectValues():
                if (hasattr(child,"isInstanceOf") and not child.isInstanceOf("Event")) or not hasattr(child,"isInstanceOf"):
                    if (not includeDisabled) and child.getIsDisabled(): continue
                    children = children + [child]

        #separate the ordered and non-ordered children
        orderedChildren = []
        nonOrderedChildren = []
        for child in children:
            if hasattr(child, "order"):
                orderedChildren = orderedChildren + [child]
            else:
                nonOrderedChildren = nonOrderedChildren + [child]

        #sort the ordered children
        orderedChildren.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

        return orderedChildren + nonOrderedChildren

    #---------------------------------------------------------------#

    def getChildrenByInstanceOf(self, className):
        """
            Returns a sequence of the immediate child objects
            that are of the specified instance type (using
            GenericObject.isInstanceOf() method)
        """

        #no children to start with!
        result = []

        #is it a container?
        if self.isContainer():
            for child in self.objectValues():
                if hasattr(child,"isInstanceOf") and child.isInstanceOf(className):
                    result = result + [child]

        return result

    #---------------------------------------------------------------#

        
    def getDescendantsByInstanceOf(self, className):
        """
            Returns a list of all available components implementing the specified interface
        """
        
        all = []
        for object in self.getChildren():
            if object.isInstanceOf(className):
                all.append( object )
            
            if object.isContainer():
                all.extend( object.getDescendantsByInstanceOf(className) )
                
        return all
        
    #---------------------------------------------------------------#    
    
    def getDescendantsByOrder(self):
        """
            Returns a list of the ordered child descendants in 
            the container in order.

            Returns [] if there are no orderable children or the
            container doesn't support ordering.
        """

        #does the container support ordering
        if self.isContainer() and self.isOrderedContainer():

            #create the descendants
            descendants = [self]
            for child in self.getChildrenByOrder():
                descendants = descendants + child.getDescendantsByOrder()
            
            return descendants
        else:
            return [self]

    #---------------------------------------------------------------#

    def getChildrenByOrder(self):
        """
            Returns a list of the orderable children in the container
            in order.

            Returns [] if there are no orderable children or the
            container doesn't support ordering.
        """

        #does the container support ordering
        if self.isContainer() and self.isOrderedContainer():

            #get the orderable children
            children = [child for child
                        in self.getChildren()
                        if hasattr(child, 'isOrderable') and child.isOrderable()]

            #sort the children into order
            children.sort(lambda x, y : -(x.getOrder() < y.getOrder()))
            return children
        else:
            return []

    #---------------------------------------------------------------#

    def containsChildren(self):
        """
            Return if the container contains children
        """

        return self.isContainer() and len(self.getChildren()) > 0

    #---------------------------------------------------------------#

    def getPermittedChildren(self, sortDisplayNames = false):
        """
            Return a list of ClassInfo instances for children that
            are permitted in this container
        """

        #use the KnowledgeEngineCore to determine this
        return getPermittedChildren(self.getClassInfo(), self, sortDisplayNames)

    #---------------------------------------------------------------#

    def getElements(self):
        """
            Return a list of Element objects in this container.
        """

        if self.isContainer():
            #get all the immediate children
            children = self.objectValues()

            #filter the Elements
            return filter(lambda x : x.isInstanceOf("Element"), children)

        else:
            return []

    #---------------------------------------------------------------#

    def getAcquiredElementsDictionary(self):
        """
            Return a dictionary of Elements, indexed by getId()
            including those in the parent container (recursively).

            NOTE: Like normal Zope Acquisition, local Elements
                  override parent container Elements.
        """
        
        # Check to see if this component is promoted. If so, walk up the tree to find
        # the toolbar items. 
        parent = self.getParent()
        if hasattr(parent, 'getDisplayMode') and parent.getDisplayMode() == 'promoted':
            promoted = true
        else:
            promoted = false        

        if promoted or self.isContainer():
            #is the parent a Container?
            if isinstance(parent, GenericObject) and parent.isContainer():
                result = parent.getAcquiredElementsDictionary()
            else:
                result = {}

            #add the local elements to the result
            for element in self.getElements():
                result.update({element.getId():element})

            return result

        else:
            return {}

    #---------------------------------------------------------------#

    def getAcquiredElementsByTypeDictionary(self):
        """
            Return a dictionary of Elements, indexed by getClassInfo().getClassName()
            including those in the parent container (recursively).

            NOTE: Like normal Zope Acquisition, local Element types
                  override parent container Element types
        """

        # Check to see if this component is promoted. If so, walk up the tree to find
        # the toolbar items. 
        parent = self.getParent()
        if hasattr(parent, 'getDisplayMode') and parent.getDisplayMode() == 'promoted':
            promoted = true
        else:
            promoted = false
        
        if promoted or self.isContainer():
            #is the parent a Container?
            if isinstance(parent, GenericObject) and parent.isContainer():
                result = parent.getAcquiredElementsByTypeDictionary()
            else:
                result = {}

            #add the local elements to the result
            for element in self.getElements():
                result.update({element.getClassInfo().getClassName():element})

            return result

        else:
            return {}

    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.

            This is to allow the 'Order' column in the Content
            management tab in Zope
        """

        return false

    #---------------------------------------------------------------#

    def getFirstChild(self):
        """
            Return the first orderable child in the container
            Returns None if there is no orderable first child

            PRE: self.isOrderedContainer()
        """

        #get the children in order of appearance
        children = self.getChildrenByOrder()

        #are there any ordered children
        if len(children) > 0:
            return children[0]
        else:
            return None

    #---------------------------------------------------------------#

    def getLastChild(self):
        """
            Return the last orderable child in the container
            Returns None if there is no orderable last child

            PRE: self.isOrderedContainer()
        """

        #get the children in order of appearance
        children = self.getChildrenByOrder()

        #are there any ordered children
        if len(children) > 0:
            return children[len(children)-1]
        else:
            return None

    #---------------------------------------------------------------#

    def getPreviousSibling(self):
        """
            Return the sibling above this object.
            Returns None if there is no previous sibling or this
            is the first sibling in the container

            PRE: self.isOrderable() and
                 self.getParent().isOrderedContainer()
        """

        #ensure that the object is orderable and it is in an ordered container
        if self.isOrderable() and self.getParent().isOrderedContainer():

            #get the ordered children that occur before this object
            previousSiblings = [sibling for sibling
                                in self.getParent().getChildren()
                                if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() < self.getOrder()]

            #sort the previous siblings (in descending order so the previous sibling is first)
            previousSiblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))

            #the previous sibling is the first in the list
            if len(previousSiblings) == 0:
                return None
            else:
                return previousSiblings[0]
        else:
            return None

    #---------------------------------------------------------------#

    def hasPreviousSibling(self):
        """
            Return if the object has a previous sibling.
        """

        return self.getPreviousSibling() != None

    #---------------------------------------------------------------#

    def getNextSibling(self):
        """
            Return the sibling below this object.
            Returns None if there is no next sibling or this
            is the last sibling in the container

            PRE: self.isOrderable() and
                 self.getParent().isOrderedContainer()
        """

        #ensure that the object is orderable and it is in an ordered container
        if self.isOrderable() and self.getParent().isOrderedContainer():

            #get the ordered children that occur after this object
            nextSiblings = [sibling for sibling
                            in self.getParent().getChildren()
                            if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() > self.getOrder()]

            #sort the previous siblings (in non-descending order so the next sibling is first)
            nextSiblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

            #the next sibling is the first in the list
            if len(nextSiblings) == 0:
                return None
            else:
                return nextSiblings[0]
        else:
            return None

    #---------------------------------------------------------------#

    def hasNextSibling(self):
        """
            Return if the object has a next sibling.
        """

        return self.getNextSibling() != None

    #---------------------------------------------------------------#

    def getUnhandledEventDefinitions(self):
        """
            Return a sorted collection of EventDefinitions that are
            currently unhandled by the object.  Unhandled in this
            circumstance means that the end-user hasn't added an event
            handler for an event.
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the children by event (we want a list of handled events)
        handledEvents = [e.getIdentity() for e in children if hasattr(e, "isInstanceOf") and e.isInstanceOf("Event")]

        #return a list of events that are not handled!
        return [e for e in self.getClassInfo().getEventDefinitions() if not e.getIdentity() in handledEvents]

    #---------------------------------------------------------------#

    def getHandledEvents(self):
        """
            Returns a sequence of Event's that are
            handled by this object
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the Event(s) only
        return [x for x in children if hasattr(x, "isInstanceOf") and x.isInstanceOf("Event")]

    #---------------------------------------------------------------#

    def getHandledEvent(self, eventIdentity):
        """
            Returns the specified handled event for this object.
            If the event isn't handled, None is returned.
            If the event is disabled, the event is still returned.
        """

        #find the event in the child collection
        events = [e for e in self.objectValues() if hasattr(e, "isInstanceOf") and e.isInstanceOf("Event") and e.getIdentity() == eventIdentity]

         #does the handled event exist?
        if len(events) == 1:
            return events[0]
        else:
            return None

    #---------------------------------------------------------------#

    def isEventEnabled(self, eventIdentity):
        """
            Returns the specified event is enabled (and handled) by
            this object (that is, it is defined and enabled)
        """

        #get the handled event
        event = self.getHandledEvent(eventIdentity)

        #is the event enabled?
        return event != None and event.isEnabled()

    #---------------------------------------------------------------#

    def getEnabledEvent(self, eventIdentity):
        """
            Returns the specified handled event for this object.
            If the event isn't handled, None is returned.
            If the event is disabled, None is returned.
        """

        #get the handled event
        event = self.getHandledEvent(eventIdentity)

        #is the event enabled?
        if event.isEnabled():
            return event
        else:
            return None

    #---------------------------------------------------------------#

    def createEvent(self, REQUEST=None, EVENT=None):
        """
            FINAL: This purpose of this method is to handle when
            an event is constructed using the events tab

            This method handles the following scenarios;

            1. A GET request to create an instance of an event, with
               the specified identity.  In this case, this method
               will respond with a management interface tailored for
               the requested event.
        """

        #get the proposed identity of the event from the request
        if EVENT == None:
            identity = REQUEST['EVENT']
        else:
            identity = EVENT

        #------------------
        #get the event definition
        eventDefinition = self.getClassInfo().getEventDefinition(identity)

        #create an event with the specified identity
        #(also sets the zopeid to be the identity)
        classInfo = getClassInfo('Event')

        #------------------
        #create the new instance using the class info for the desired instance type
        #(by doing this the classInfo object has the ability to control how an
        # instance is really initialised)
        event = classInfo.newInstance(eventDefinition.getIdentity(), eventDefinition.getIdentity(), REQUEST)

        #set the description of the event
        event.setDescription(eventDefinition.getDescription())

        #set that the event is enabled
        event.setIsEnabled(true)

        #------------------
        #add the instance to the container (self)
        self._setObject(eventDefinition.getIdentity(), event)

        #------------------
        #redirect to the new object (if possible)
        if REQUEST is not None and hasattr(REQUEST, 'URL1'):

            #redirect back to the folder in which we created the event
            REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/folder_contents')

        else:
            return self._getOb(event.getId())

    #---------------------------------------------------------------#

    def moveObject(self, REQUEST=None, OBJECT=None, DIRECTION=None):
        """
            Attempt to move the specified OBJECT in it's parent
        """

        #do we have an object and a direction?
        if OBJECT != None and (DIRECTION == "UP" or DIRECTION == "DOWN"):

            #get the object that we will be moving (the source object)
            srcObject = self[OBJECT]

            #ensure that the source object is orderable
            if srcObject.isOrderable():

                #determine the object we are going to be swapping places with (the destination object)
                if DIRECTION == "UP":
                    destObject = srcObject.getPreviousSibling()
                elif DIRECTION == "DOWN":
                    destObject = srcObject.getNextSibling()
                else:
                    destObject = None

                #ensure we have a destination
                if destObject != None:

                    #swap the previous sibling and this object's order
                    newOrder = destObject.getOrder()
                    oldOrder = srcObject.getOrder()

                    srcObject.setOrder(newOrder)
                    destObject.setOrder(oldOrder)

        #redirect back to the folder in which we reordered an object
        REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/folder_contents')

    #---------------------------------------------------------------#

    def manage_renameObject(self, id, new_id, REQUEST=None):
        """
            Attempt to rename the specified child object
        """

        print "manage_renameObject"

        #get the object we are meant to rename
        proposedObject = self._getOb(id)

        #is the object a generic object?
        if isinstance(proposedObject, GenericObject):

            #ensure that the new id is ok
            try:
                self._checkId(new_id)
            except:
                #redirect back to the management page for this object
                #determine the GUI so we know the page to which we should return
                if REQUEST != None and hasattr(REQUEST, "GUI") and REQUEST["GUI"] == "CMF":
                    raise CopyError, MessageDialog(title='Invalid Id', message=sys.exc_info()[1], action ='folder_contents')

                else:
                    raise CopyError, MessageDialog(title='Invalid Id', message=sys.exc_info()[1], action ='manage_main')

            #ensure that the object is moveable (as a rename cause a temporary move)
            if not proposedObject.cb_isMoveable():
                raise CopyError, eNotSupported % id

            #notify the object of the temporary move
            try:
                proposedObject._notifyOfCopyTo(self, op=1)
            except:
                #redirect back to the management page for this object
                #determine the GUI so we know the page to which we should return
                if REQUEST != None and hasattr(REQUEST, "GUI") and REQUEST["GUI"] == "CMF":
                    raise CopyError, MessageDialog(title='Rename Error', message=sys.exc_info()[1], action ='folder_contents')
                else:
                    raise CopyError, MessageDialog(title='Rename Error', message=sys.exc_info()[1], action ='manage_main')

            #remove the object and add it as a different name
            
            print "RENAMING"
                      
            self._delObject(id)
            proposedObject = aq_base(proposedObject)
            proposedObject._setId(new_id)

            #Note - because a rename always keeps the same context, we
            #can just leave the ownership info unchanged.
            self._setObject(new_id, proposedObject, set_owner=0)

            if REQUEST is not None:
                #redirect back to the management page for this object
                #determine the GUI so we know the page to which we should return
                if hasattr(REQUEST, "GUI") and REQUEST["GUI"] == "CMF":
                    return self.folder_contents(self, REQUEST, update_menu=1)
                else:
                    return self.manage_main(self, REQUEST, update_menu=1)

            return None

        else:
            #use the super class manage_renameObject
            return PloneFolder.manage_renameObject(self, id, new_id, REQUEST)

    #---------------------------------------------------------------#

    def _verifyObjectPaste(self, object, validate_src=true):
        """
            FINAL: Attempt to verify that the proposed paste operation
            ok to proceed with.
        """

        #set the proposed child & whether we should validate the proposed child
        proposedChild = object
        validateChild = validate_src

        #determine the type of operation
        isImport = not validate_src
        isCopyOrMove = not isImport

        #assume paste is not permitted
        isPastePermitted = false

        #does the child have classinfo?
        #(only knowledge engine components are permitted in a knowledge engine tree)
        if not hasattr(proposedChild,"getClassInfo"):
            #we are pasting a ZOPE object

            #get the meta types for this container as the container may permit zope objects
            zopeMetaTypes = self.getZopeMetaTypes()

            #attempt to locate the type of the proposed child in the zope meta types for this container
            #TODO: this should also check the children of the proposed child, as they too may not be permitted in this container
            isPastePermitted = false
            for meta in zopeMetaTypes:
                if meta['name'] == proposedChild.meta_type:
                    isPastePermitted = true
                    break

            #is the paste permitted?
            if not isPastePermitted:
                raise CopyError, MessageDialog(title='Invalid Paste Operation',
                                               message='Cannot paste non-Knowledge Engine objects into %s %s.' % (self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='folder_contents')

        else:

            #does this container permit this proposedChild as a child?
            if not self.getClassInfo().isChildPermitted(proposedChild.getClassInfo(), proposedChild):
                raise CopyError, MessageDialog(title='Invalid Operation',
                                               message='%s %s cannot exist in %s %s.' % (proposedChild.getClassInfo().getClassIconHTML(), proposedChild.getTitleOrId(), self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='folder_contents')

            #does the proposed child think it is allowed in this container?
            if not proposedChild.getClassInfo().isPermittedInContainer(self.getClassInfo(), self, proposedChild):
                raise CopyError, MessageDialog(title='Invalid Operation',
                                               message='%s %s cannot exist in %s %s.' % (proposedChild.getClassInfo().getClassIconHTML(), proposedChild.getTitleOrId(), self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='folder_contents')

            #paste is permitted
            isPastePermitted = true

        #TODO: check security permission for the user

        #otherwise paste operation is OK!
        return isPastePermitted

    #---------------------------------------------------------------#

    def manage_delObjects(self, ids=[], REQUEST=None):
        """
            Delete a subordinate object
            The objects specified in 'ids' get deleted.
        """

        #get the objects we are about to delete
        objectsToDelete = [self[id] for id in ids]

        #are any of the objects to be deleted Events
        #(we need to redirect to the appropriate tab)
        nrEvents = len([obj for obj in objectsToDelete if hasattr(obj,'isInstanceOf') and obj.isInstanceOf('Event')])

        #delegate the deletion to the super class
        result = PloneFolder.manage_delObjects(self, ids, None)

        #was a result provided (might be an error)
        if result is None:
            if REQUEST is not None and hasattr(REQUEST, 'URL1'):
                try:
                    url = self.DestinationURL()
                except:
                    url = REQUEST['URL1']

                if nrEvents > 0:
                    REQUEST.RESPONSE.redirect(url + '/eventEditor')
                else:
                    if hasattr(REQUEST, "GUI") and REQUEST["GUI"] == "CMF":
                        REQUEST.RESPONSE.redirect(url + '/folder_contents')
                    else:
                        REQUEST.RESPONSE.redirect(url + '/manage_workspace')

        else:
            return result

    #---------------------------------------------------------------#

    def getIcon(self, relative_to_portal=0):
        """
            CMF Support:

            Returns a URL to the icon for the object
        """

        return self.getClassInfo().getIcon()

    #---------------------------------------------------------------#

    def Title(self):
        """
            CMF Support:

            Supports the CMF Title() method for returning the title
            of an object
        """

        return self.getTitle()

    #---------------------------------------------------------------#
    
    def __call__(self, REQUEST, **args):
        """
            PLONE Support:

            This method is called when the / for an object is
            requested.  Here we pass the request to the 'view'
            to handle the request.
        """

        return self.view(REQUEST, **args)

    #---------------------------------------------------------------#

    security.declareProtected('Modify portal content', 'view')
    def view(self, REQUEST, **args):
        """
            PLONE Support:

            Return the default PLONE <object>_view for the object

            If the component supports a default view
            (ie: self.hasDefaultView) then we return the view for the object.
            If not, redirect the closest parent that does have a default view.
        """

        #TODO: ensure this object has a default view, otherwise redirect to the parent that has a default view.

        if self.getClassInfo().isContainer():
            return self.folder_contents(REQUEST=self.REQUEST)
        else:
            return self.edit_form(REQUEST=self.REQUEST)

        #dynamically determine the view function for this object
        #return eval("self.%s_view(self, REQUEST, **args)" % self.getClassInfo().getClassName())

    #---------------------------------------------------------------#

    def createObject(self, REQUEST=None, CLASSNAME=None, PROPERTIES=None, IGNORE_CONSTRUCTOR=False, id=None, identity=None):
        """
            FINAL: This purpose of this method is to handle when
            an object is constructed using the property editor or
            by a direct call with a specified CLASSNAME (corresponding
            to a KnowledgeEngine registered ClassInfo class)

            This method handles the following scenarios;

            1. A POST request to create an instance of a registered
               Knowledge Engine class that requires a
               constructor property editor.  In this case, this method
               will respond with a property editor tailored for
               the requested class using the property editor (in the constructor mode).

            2. A POST request to create an instance of a registered
               Knowledge Engine class that DOES NOT require a
               constructor property editor. That is, all properties
               have default values that should not be edited by the
               end-user when creating and instance!  In this case,
               this method will create a default instance of the
               requested class and add it as a child to this class.

            3. A POST request containing the property values from
               a constructor property editor for a particular
               registered Knowledge Engine class. (the POST request
               will originate from the property editor)
               In this case, this method will create an instance of the
               requested class, set the properties of the instance
               according to those defined in the request
               and add it as a child to this class.

            4. A program call that passes a PROPERTIES dictionary with the keid and
               other necessary properties. Because no REQUEST is passed to this
               object, it does not attempt to respond a RESPONSE redirect but returns
               the newly created object

        """

        if PROPERTIES == None:
            PROPERTIES = REQUEST

        #get the classinfo name from the request
        if CLASSNAME == None:
            className = PROPERTIES['CLASSNAME']
        else:
            className = CLASSNAME

        #get the registered classinfo instance from the core for the specified class
        classInfo = getClassInfo(className)

        #determine if the class supports constructor properties
        classSupportsConstructorProperties = classInfo.hasEditableConstructorProperties()

        #create a new identity for the object
        if not identity: identity = generateIdentity(classInfo.getIdentityType())

        #is this request to create a constructor form for the specified class
        #(to get initial object properties from the user?)
        if classSupportsConstructorProperties and not IGNORE_CONSTRUCTOR:

            #return the property editor for the specified classinfo - in constructor mode
            return self.edit_form(REQUEST=PROPERTIES, ISCONSTRUCTOR=true, CLASSINFO=classInfo)

        else:
            #the request is not for an initial constructor!
            #it is for an object that does not require a constructor property editor

            #------------------
            #the class doesn't use constructor property editing so
            #use the property reflection interface to retrieve the default id!
            if not id: id = classInfo.getPropertyDefinition('id').getDefaultValue()

            #------------------
            #create the new instance using the class info for the desired instance type
            #(by doing this the classInfo object has the ability to control how an
            # instance is really initialised)
            instance = classInfo.newInstance(id, identity)

            #------------------
            #ensure that all properties are set to their defaults
            for propertyDefinition in instance.getPropertyDefinitions():

                #does the instance have the current property?
                if not hasattr(instance, propertyDefinition.getIdentity()):

                    #value is missing for the property, so add the default value
                    setattr(instance, propertyDefinition.getIdentity(), propertyDefinition.getDefaultValue())

            #------------------
            #add the instance to the container (self)
            self._setObject(instance.getId(), instance)

            #------------------
            #set the position of the instance in the container (if they are ordered)
            #(we have to get the object from the contain so it get's wrapped correctly)
            self._getOb(instance.getId()).setAsLast()

            #------------------
            #give the object an opportunity to handle an after 'editing' event
            self._getOb(instance.getId()).onAfterEdit()

            #------------------
            #redirect to the new object (if possible)
            if REQUEST is not None and hasattr(REQUEST, 'URL1'):
                try:
                    url = self.DestinationURL()
                except:
                    url = REQUEST['URL1']

                #determine the GUI so we know the page to which we should return
                REQUEST.RESPONSE.redirect(url + '/folder_contents')

            else:
                return self._getOb(instance.getId())

    #---------------------------------------------------------------#

    security.declareProtected('Modify portal content', 'edit_form')
    def edit_form(self, REQUEST, **args):
        """
            PLONE Support:

            The purpose of this method is the handle BOTH the GET and POST
            request for editing component properties via the
            GenericObject_edit_form.pt

            NOTE: The responsibility for actually setting component
            property values is delegated to each of property editlets
            generated by the property editor form GenericObject_edit_form.pt
        """

        #-----------------------------
        #get the class info that we will be editing
        #(is the class info being provided? this occurs if we are attempting to construct a new instance using the property editor)
        if args.has_key('CLASSINFO'):
            classInfo = args['CLASSINFO']

        elif REQUEST is not None and REQUEST.has_key('CLASSNAME'):
            #the request contains the class info for the object we are editing
            classInfo = getClassInfo(REQUEST['CLASSNAME'])

        else:
            #we must be editing ourselves
            classInfo = self.getClassInfo()

        #-----------------------------
        #determine if we are in 'constructor' mode
        isForConstructor = (args.has_key('ISCONSTRUCTOR') and args['ISCONSTRUCTOR']) or (REQUEST.has_key('ISCONSTRUCTOR') and REQUEST['ISCONSTRUCTOR'])

        #-----------------------------
        #determine the type of action we have to perform (save, create, cancel, edit)
        if REQUEST.has_key('form.button.Cancel'):
            action = "CANCEL"

        elif REQUEST.has_key('form.button.Save'):
            action = "SAVE"

        elif REQUEST.has_key('form.button.Create'):
            action = "CREATE"

        else:
            action = "EDIT"

        errors = args.get('errors', {})

        #-----------------------------
        #perform the appropriate action
        if action == "EDIT":
            #is the editor for a constructor?
            if isForConstructor:
                #return an editor form for the required class in constructor mode
                return self.GenericObject_edit_form(self, REQUEST, ISCONSTRUCTOR=true, CLASSINFO=classInfo, PROPERTYDEFINITIONS=classInfo.getPropertyDefinitions(), EDITINGOBJECT=None, errors=errors)
            else:
                #return an editor form for this instance in editor mode
                return self.GenericObject_edit_form(self, REQUEST, ISCONSTRUCTOR=false, CLASSINFO=classInfo, PROPERTYDEFINITIONS=self.getPropertyDefinitions(), EDITINGOBJECT=self, errors=errors)

        elif action == "CANCEL":

            #return an editor form for this instance in editor mode
            return self.GenericObject_edit_form(self, REQUEST, ISCONSTRUCTOR=false, CLASSINFO=classInfo, PROPERTYDEFINITIONS=self.getPropertyDefinitions(), EDITINGOBJECT=self)

        elif action == "SAVE":
            #determine all of the editlets for properties
            #of the object, and have them work out and set the
            #appropriate property values on the object using the information contained
            #in the posted REQUEST.
            
            print "Save"
            
            self.onBeforeEdit()
            
            for propertyDefinition in self.getPropertyDefinitions():

                #get the property editlet (using the current value of the property)
                editlet = propertyDefinition.getEditlet(self, self.getPropertyValue(propertyDefinition.getIdentity()), isForConstructor=false, REQUEST=REQUEST)

                #update the property value using the editlet if and only if the REQUEST contains a value for the property
                if editlet is not None and editlet.containsPropertyValue(propertyDefinition, REQUEST):
                    try:
                        #have the editlet set the new value from the request
                        editlet.updatePropertyWith(propertyDefinition, self, REQUEST)
                    except ValidationException, e:
                        errors[propertyDefinition.getIdentity()] = e.value    

            #------------------
            #give the object an opportunity to handle an after 'editing' event
            print "before onAfterEdit"
            self.onAfterEdit()

            #now that we are done saving, let's continue to edit the same component
            return self.GenericObject_edit_form(self, REQUEST, ISCONSTRUCTOR=false, CLASSINFO=classInfo, PROPERTYDEFINITIONS=self.getPropertyDefinitions(), EDITINGOBJECT=self, errors=errors)

        else: #action == "CREATE"
            #------------------
            #create a new identity for the object
            identity = generateIdentity(classInfo.getIdentityType())

            #------------------
            #create the new instance using the class info for the desired instance type
            #(by doing this the classInfo object has the ability to control how an
            # instance is really initialised)
            instance = classInfo.newInstance(identity, identity, REQUEST)

            #------------------
            #determine all of the editlets for properties
            #for the new instance and have them work out and set their
            #appropriate property values using the information contained
            #in the posted REQUEST.
            for propertyDefinition in instance.getPropertyDefinitions():

                #get the property editlet (using the current value of the property)
                editlet = propertyDefinition.getEditlet(instance, propertyDefinition.getDefaultValue(), isForConstructor=true, REQUEST=REQUEST)

                #update the property value using the editlet if and only if the REQUEST contains a value for the property
                if editlet is not None and editlet.containsPropertyValue(propertyDefinition, REQUEST):
                    try:
                        #have the editlet set the new value from the request
                        editlet.updatePropertyWith(propertyDefinition, instance, REQUEST)
                    except ValidationException, e:
                        errors[propertyDefinition.getIdentity()] = e.value

            # Make sure we trap any errors encountered
            if errors:
                #now that we are done saving, let's continue to edit the same component
                return self.GenericObject_edit_form(self, REQUEST, ISCONSTRUCTOR=true, CLASSINFO=classInfo, PROPERTYDEFINITIONS=instance.getPropertyDefinitions(), EDITINGOBJECT=self, errors=errors)

            #------------------
            #ensure that all non-edited properties are set to their defaults
            for propertyDefinition in instance.getPropertyDefinitions():

                #does the instance have the current property?
                if not hasattr(instance, propertyDefinition.getIdentity()):

                    #value is missing for the property, so add the default value
                    setattr(instance, propertyDefinition.getIdentity(), propertyDefinition.getDefaultValue())

            #------------------
            #add the instance to the container (self)
            self._setObject(instance.getId(), instance)

            #------------------
            #set the position of the instance in the container (if they are ordered)
            #(we have to get the object from the container so it get's wrapped correctly)
            self._getOb(instance.getId()).setAsLast()

            #------------------
            #give the object an opportunity to handle an after 'editing' event
            self._getOb(instance.getId()).onAfterEdit()

            #redirect back to the folder in which we created the object
            REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/folder_contents')

    #---------------------------------------------------------------#

    def permission_form(self, REQUEST, **args):
        """
            PLONE Support:

            The purpose of this method is the handle BOTH the GET and POST
            request for editing component permissions via the
            GenericObject_permisison_form.pt
        """

        #let's use a sensible case for the REQUEST
        request = REQUEST

        #let's get the session from the request
        session = request.SESSION

        #get the user id of the currently authenticated user
        zopeUserIdentity = REQUEST["AUTHENTICATED_USER"].getId()
        userIdentity = iif(zopeUserIdentity is None, "anonymous", zopeUserIdentity)

        #get the User from the UserProviderService (we may even store it in the session!)
        userProviderService = getUserProviderService(self)
        user = userProviderService.getUser(userIdentity)

        #we assume the specific permission is undefined
        permission = None

        #let's determine the permission (definition) we'll be displaying
        if request["REQUEST_METHOD"] == "GET" and not request.form.has_key("mode"):
            #display the first
            displayPermissionDefinition = self.getClassInfo().getPermissionDefinitions()[0]

            #on a get request, we are in the 'DISPLAY' mode
            mode = 'DISPLAY'
        else:
            #handle the action request

            #get the permission identity we are displaying/editing
            if request.form.has_key("permissionIdentity"):
                permissionIdentity = request["permissionIdentity"]
            else:
                permissionIdentity = request["cmbPermissionDefinition"]

            #handle a delete request
            if request.form.has_key("form.button.Delete"):
                #get the security service so we can look up permissions
                securityService = getSecurityService(self)

                #get all of the permissions so we can determine which one(s) to delete
                permissions = securityService.getPermissions(self.getIdentity())

                #delete the selected permissions (that are in the request)
                for aPermission in permissions:

                    #is the current permission in the request to be deleted?
                    if request.form.has_key("chkPermission%s%s%s" % (aPermission.getIdentity(), aPermission.getSecurableIdentity(), aPermission.getActorIdentity())):
                        securityService.removePermission(aPermission)

                #after delete go back to the 'DISPLAY' mode
                mode = 'DISPLAY'

            #was add requested?
            elif request.form.has_key("form.button.Add"):
                #create a template permission to edit
                permission = Permission(permissionIdentity, self.getIdentity(), "*", true)

                #we are now in the 'CREATE' mode
                mode = 'CREATE'

            #was save requested
            elif request.form.has_key("form.button.Save"):
                #get the security service so we can look up the permission to edit
                securityService = getSecurityService(self)

                #get the existing permission (if we are editing)
                if request["mode"] == "EDIT":
                    permission = securityService.getSpecificPermission(permissionIdentity, request["originalSecurableIdentity"], request["originalActorIdentity"])

                else:
                    #get the securable identity
                    securableIdentity = request["cmbSecurableIdentity"]

                    #get the actor identity (we may have to get the specific actor!)
                    actorIdentity = request["cmbActorIdentity"]
                    if actorIdentity not in ["*", "anonymous"]:
                        # actorIdentity = request["actorIdentity"]
                        actorIdentity = request["cmbActor"]

                    #attempt to get the existing permission
                    permission = securityService.getSpecificPermission(permissionIdentity, securableIdentity, actorIdentity)

                    #create a permission if one doesn't already exist
                    if permission is None:
                        permission = Permission(permissionIdentity, securableIdentity, actorIdentity, true)

                #set the attributes of the permission from the request
                if request["cmbStatus"] == "granted":
                    permission.grant()
                else:
                    permission.deny()

                #attempt to set the effective from time
                try:
                    permission.setInstantEffectiveFrom(DateTime(request["effectiveFrom"]))
                except:
                    permission.setInstantEffectiveFrom(None)

                #set the expiry time
                try:
                    permission.setInstantExpiresOn(DateTime(request["expiresOn"]))
                except:
                    permission.setInstantExpiresOn(None)

                #set the credit management strategy
                try:
                    permission.setCreditStrategy(request["cmbCreditStrategy"])
                except:
                    permission.setCreditStrategy(CS_NOT_APPLICABLE)

                # Set the assessmentscope if the permission uses it
                permissionDefinition = self.getClassInfo().getPermissionDefinition(permission.getIdentity())
                if permissionDefinition.getUsesAssessmentScope():
                    try:
                        # Get the selected list for the assessment scope
                        # 
                        # 20070308 johnm    At the very least, ensure that scope is
                        #                   set to (All) / '*'
                        lstAssessmentScope = request.get("lstAssessmentScope", '*')

                        #was a list of items selected?
                        if type(lstAssessmentScope) is types.ListType:
                            #ensure that if '*' is selected, nothing else is selected
                            if '*' in lstAssessmentScope:
                                permission.setAssessmentScope(['*'])
                            else:
                                permission.setAssessmentScope(lstAssessmentScope)
                        else:
                            permission.setAssessmentScope([lstAssessmentScope])
                    except:
                        permission.setAssessmentScope([])

                #are we saving the permission or going on to select a user
                if request.form.has_key("form.button.Save"):
                    #save the changed permission (will create a new one if it doesn't already exist)
                    securityService.savePermission(permission)

                    #we are now in the 'DISPLAY' mode
                    mode = 'DISPLAY'

            #was cancel requested?
            elif request.form.has_key("form.button.Cancel"):
                #we are now in the 'DISPLAY' mode
                mode = 'DISPLAY'

            #was edit requested
            elif request.form.has_key("mode") and request["mode"] == "EDIT":
                #get the security service so we can look up the permission to edit
                securityService = getSecurityService(self)

                #get the permission to edit
                permission = securityService.getSpecificPermission(request["permissionIdentity"], request["securableIdentity"], request["actorIdentity"])

                #we are now in the 'EDIT' mode
                mode = 'EDIT'

            #was credit management requested?
            elif request.form.has_key("mode") and request["mode"] == "MANAGECREDITS":
                #get the security service so we can look up the permission to edit
                securityService = getSecurityService(self)

                #get the permission for which to manage credits
                permission = securityService.getSpecificPermission(request["permissionIdentity"], request["securableIdentity"], request["actorIdentity"])

                #are we making a deposit/withdraw?
                if request.form.has_key("form.button.Deposit") or request.form.has_key("form.button.Withdraw"):
                    #get the credit management service
                    creditManagementService = getCreditManagementService(self)
                
                    #get the narration and amount
                    narration = request["txtNarration"]
                    try:
                        amount = int(request["numAmount"])
                        if amount < 0:
                            amount = 0
                    except:
                        amount = 0
                        
                    #if we have an amount
                    if amount != 0:
                        if request.form.has_key("form.button.Deposit"):
                            #are we depositing for a group?                        
                            if request.form.has_key("optCreditOption"):
                                if request["optCreditOption"] == "SHARED":
                                    #credits are shared by the group members so we define the credit on the group itself
                                    actorIdentities = [permission.getActorIdentity()]
                                else:
                                    #credits are for each member of the group, so the need to apply the credit to each group member
                                    groupProviderService = getGroupProviderService(self)
                                    actorIdentities = groupProviderService.getUserIdentitiesInGroup(permission.getActorIdentity())                                    
                            else:
                                actorIdentities = [permission.getActorIdentity()]
                        
                            #do the deposit for all of the actors
                            for actorIdentity in actorIdentities:
                                creditManagementService.depositCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity, user.getIdentity(), amount, narration)
                            
                        elif request.form.has_key("form.button.Withdraw"):
                            #are we withdrawing for a group?                        
                            if request.form.has_key("optCreditOption"):
                                if request["optCreditOption"] == "SHARED":
                                    #credits are shared by the group members so we define the credit on the group itself
                                    actorIdentities = [permission.getActorIdentity()]
                                else:
                                    #credits are for each member of the group, so the need to apply the credit to each group member
                                    groupProviderService = getGroupProviderService(self)
                                    actorIdentities = groupProviderService.getUserIdentitiesInGroup(permission.getActorIdentity())                                    
                            else:
                                actorIdentities = [permission.getActorIdentity()]
    
                            #do the withdrawal for all of the actors
                            for actorIdentity in actorIdentities:
                            
                                #ensure we don't overdraw!
                                balanceAvailable = creditManagementService.getAvailableCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity)                                
                                if balanceAvailable < amount:
                                    amount = balanceAvailable
                            
                                #do the withdraw for the actor
                                creditManagementService.withdrawCredits(permission.getIdentity(), permission.getSecurableIdentity(), actorIdentity, user.getIdentity(), amount, narration)

                #we are now in the 'MANAGECREDITS' mode
                mode = 'MANAGECREDITS'

            else:
                #some weird request... let's default to 'DISPLAY'
                mode = 'DISPLAY'

            displayPermissionDefinition = self.getClassInfo().getPermissionDefinition(permissionIdentity)

        #render the permission editor form
        return self.GenericObject_permission_form(self, REQUEST, MODE=mode, ISCONSTRUCTOR=(mode != 'DISPLAY'), EDITINGOBJECT=self, PERMISSION=permission, DISPLAYPERMISSIONDEFINITION=displayPermissionDefinition, getToolByName=getToolByName)

    #---------------------------------------------------------------#

    def preview(self, REQUEST, **args):
        """
            CMF Support:

            A wrapper over the GenericObject_preview.dtml DTML document
        """

        REQUEST["REFRESH_JAVASCRIPT"] = []
        REQUEST["DECLARATIONS_JAVASCRIPT"] = {}     #a dictionary (set) to prevent individual component types duplicating declarations
        REQUEST["ONLOAD_JAVASCRIPT"] = {}           #a dictionary (set) to prevent individual component types duplicating onload statements
        REQUEST["ISPREVIEW"] = false
        REQUEST["ASSESSMENT"] = Assessment('Dummy', self.getModel())

        return self.GenericObject_preview(self, REQUEST, **args)

    #---------------------------------------------------------------#

    security.declareProtected('List folder contents', 'folder_contents')
    def folder_contents(self, REQUEST, **args):
        """
            PLONE Support:

            A wrapper over the GenericObject_folder_explorer DTML document
        """

        return self.GenericObject_folder_contents(self, REQUEST, **args)
        #return self.oldfc(self, REQUEST, **args)

    #---------------------------------------------------------------#

    def getAccessories(self):
        """
            Return the accessories that are applicable on this
            GenericObject instance.
            
            RETURN TYPE: [Accessory]
        """
        
        #filter out accessories that aren't available on the object
        result = [accessory for accessory in getRegisteredAccessories() if accessory.isAvailableForGenericObject(self)]

        #sort the accessories by title        
        result.sort(lambda x, y : -(x.getTitle(self) < y.getTitle(self)))        
            
        return result

    #---------------------------------------------------------------#

    def hasAccessories(self):
        """
            Return if there are any applicable accessories on this
            GenericObject instance.
            
            RETURN TYPE: [Accessory]
        """
        
        return len(self.getAccessories()) > 0

    #---------------------------------------------------------------#

    def accessories_form(self, REQUEST, **args):
        """
            PLONE Support:

            The purpose of this method is the handle BOTH the GET and POST
            request for generating and applying accessories via the
            GenericObject_accessories_form.pt
        """
    
        #let's use a sensible case for the REQUEST
        request = REQUEST
    
        #get the session from the request
        session = request["SESSION"]
        
        #get the response from the request (weird I know, but that's where it lives in Zope)
        response = request["RESPONSE"]
        
        #are we processing an accessory wizard step? or choosing the wizard (rendering based on a get request)
        if request["REQUEST_METHOD"] == "POST":
        
            #a new request?
            if request.form.has_key('mode'):
                #a new accessory execution
                isNewAccessoryRequest = true
                
                #determine the accessory we will be executing
                accessory = None
                for anAccessory in getRegisteredAccessories():
                    if request.has_key(anAccessory.getIdentity()):
                        accessory = anAccessory
            
                #create new wizard state
                wizardState = WizardState()

                #initialise the wizard state with default property state
                for propertyDefinition in accessory.getPropertyDefinitionsForWizard(wizardState, self):
                    wizardState.setPropertyValue(propertyDefinition.getIdentity(), propertyDefinition.getDefaultValue())

                #set the accessory we are executing
                wizardState.setPropertyValue("accessoryIdentity", accessory.getIdentity())

            #an existing request
            else:
                wizardState = session.get('wizardState')
                isNewAccessoryRequest = false

            #get the accessory
            accessory = getAccessory(wizardState.getPropertyValue("accessoryIdentity"))

            #do we need to process wizard state?
            if accessory.getTotalWizardSteps(wizardState, self) > 0:
                #do we need to render the wizard editor? (assume no)
                renderWizard = false
                
                #process the wizard action
                if not isNewAccessoryRequest:
                    #determine all of the editlets for properties of the current wizardState, and have them work out and set the
                    #appropriate property values in the wizardState using the information contained in the posted REQUEST.
                    for propertyDefinition in accessory.getPropertyDefinitionsForWizardStep(wizardState, self):

                        #get the property editlet (using the current value of the property)
                        editlet = propertyDefinition.getEditlet(propertyAccess=wizardState, context=self)

                        #does the property have an editlet
                        if editlet != None:
                            #have the editlet set the new value from the request
                            editlet.updatePropertyWith(propertyDefinition, wizardState, REQUEST)
                        else:
                            print 'no editlet for %s' % propertyDefinition.getIdentity()

                    #what was the wizard action? cancel, back or next?
                    #cancel?
                    if request.has_key("button.cancel"):
                        return self.GenericObject_accessories_form(self, REQUEST, EDITINGOBJECT=self, SUCCESS=None, MESSAGE=None, **args)

                    #back?
                    elif request.has_key("button.back"):
                        #always render on 'back'
                        renderWizard = true

                        #move backwards iff we aren't at the first step
                        if not accessory.isAtFirstWizardStep(wizardState, self):
                            wizardState.previousWizardStep()

                    #next?
                    elif request.has_key("button.next"):
                        #always render on 'next'
                        renderWizard = true                            

                        #move forwards iff we aren't at the last step
                        if not accessory.isAtLastWizardStep(wizardState, self):
                            wizardState.nextWizardStep()

                    #finished!                    
                    else:
                        #now execute the accessory
                        renderWizard = false
                
                else:
                    #a get request so we must render the wizard
                    renderWizard = true

                #add/update the wizardState to the session
                session['wizardState'] = wizardState
                    
                #render the wizard step?
                if renderWizard:
                    #use the wizard editor to get the wizard state for the report
                    return self.Accessory_wizard_form(REQUEST=request, EDITINGOBJECT=self, ACCESSORY=accessory, WIZARDSTATE=wizardState)

            #now execute and return the result
            return accessory.execute(self, wizardState, request)
            
        #a get request (not in wizard step yet)... so let the user choose an accessory to use.
        else:
            return self.GenericObject_accessories_form(self, REQUEST, EDITINGOBJECT=self, SUCCESS=None, MESSAGE=None, **args)

    #---------------------------------------------------------------#

    def listActions(self, info=None):
        """
            PLONE Support:

            Returns a tuple of ActionInformation instances
            specifying the available CMF Actions for the object

            overrides:

            (CMF Actions are different from Knowledge Engine Actions)
        """

        #no CMF actions to start with
        lCMFActions = ()

        #is the object a container? (or does it permit events?)
        if self.isContainer() or self.getClassInfo().hasEventDefinitions():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='foldercontents',
                                title='contents',
                                description='Explore the contents of the Object',
                                category='object',
                                condition='',
                                permissions=(permissions.ListFolderContents,),
                                priority=1,
                                visible=1,
                                action='string:${object_url}/folder_contents'
                            ),)

        #does the object have a default view properties?
        if self.hasDefaultView():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='view',
                                title='View',
                                description='The default view of the object',
                                category='object',
                                condition='',
                                permissions=(permissions.View,),
                                priority=2,
                                visible=1,
                                action='string:${object_url}/view'
                            ),)

        #does the object have editable properties?
        if self.hasEditableProperties():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='edit',
                                title='Edit',
                                description='Modify Properties for the Object',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=3,
                                visible=1,
                                action='string:${object_url}/edit_form'
                            ),)

        #does the object have permissions (and are they editable by the current user)?
        if self.getClassInfo().hasPermissionDefinitions():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='permissions',
                                title='Permissions',
                                description='Modify Permissions for the Object',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=3,
                                visible=1,
                                action='string:${object_url}/permission_form'
                            ),)

        #does the object have accessories (and are they editable by the current user)?
        if self.hasAccessories():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='accessories',
                                title='Accessories',
                                description='Accessories applicable to the Object',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=4,
                                visible=1,
                                action='string:${object_url}/accessories_form'
                            ),)

        #does the object support the Preview management tab?
        if self.isPreviewPermitted():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='preview',
                                title='Preview',
                                description='Preview the Object',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=5,
                                visible=1,
                                action='string:${object_url}/preview'
                            ),)

        # Give each Model a way of jumping directly to it's myknowledge
        # area
        if self.getClassInfo().getClassName() == 'Model':
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='myknowledge',
                                title='MyKnowledge',
                                description='MyKnowledge Area of the Model',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=5,
                                visible=1,
                                action='string:${object_url}/myknowledge?modelIdentities:list=%s' % self.getIdentity()
                            ),)
                            
        # Give each Model a way of jumping directly to it's myknowledge
        # area
        if self.getClassInfo().isContainer():
            lCMFActions = lCMFActions + (
                            ActionInformation(
                                id='importXML_form',
                                title='Import',
                                description='Import this object',
                                category='object',
                                condition='',
                                permissions=(permissions.ManageProperties,),
                                priority=5,
                                visible=1,
                                action='string:${object_url}/importXML_form',
                            ),)
                                                    
        lCMFActions = lCMFActions + (
                        ActionInformation(
                            id='exportXML_form',
                            title='Export',
                            description='Export this object',
                            category='object',
                            condition='',
                            permissions=(permissions.ManageProperties,),
                            priority=5,
                            visible=1,
                            action='string:${object_url}/exportXML_form',
                        ),)                                                                                

        #we've now worked out the actions...
        return lCMFActions

    #---------------------------------------------------------------#
    # Cataloging methods
    #---------------------------------------------------------------#
    # because we have a custom catalog we need to specify it explicitly here

    def getCatalogs(self):
        """ return list of catalogs the object should be index against
        """

        return [ getToolByName(self, 'portal_catalog', None)
               , getToolByName(self, 'knowledgeengine_genericobjectcatalogservice', None)
               ]

    #---------------------------------------------------------------#

    security.declareProtected(ModifyPortalContent, 'indexObject')
    def indexObject(self):
        """
            Index the object in the portal catalog.
        """
        for catalog in self.getCatalogs():
            if catalog is not None:
                catalog.indexObject(self)

    #---------------------------------------------------------------#

    security.declareProtected(ModifyPortalContent, 'unindexObject')
    def unindexObject(self):
        """
            Unindex the object from the portal catalog.
        """
        
        for catalog in self.getCatalogs():
            if catalog is not None:
                catalog.unindexObject(self)

    #---------------------------------------------------------------#

    security.declareProtected(ModifyPortalContent, 'reindexObject')
    def reindexObject(self, idxs=[]):
        """
            Reindex the object in the portal catalog.
            If idxs is present, only those indexes are reindexed.
            The metadata is always updated.

            Also update the modification date of the object,
            unless specific indexes were requested.
        """        
        if idxs == []:
            # Update the modification date.
            if hasattr(aq_base(self), 'notifyModified'):
                self.notifyModified()

        for catalog in self.getCatalogs():
            if catalog is not None:
                catalog.reindexObject(self, idxs=idxs)

    #---------------------------------------------------------------#

    security.declareProtected(ModifyPortalContent, 'reindexObjectSecurity')
    def reindexObjectSecurity(self):
        """
            Reindex security-related indexes on the object
            (and its descendants).
        """
        for catalog in self.getCatalogs():
            if catalog is not None:
                path = '/'.join(self.getPhysicalPath())
                for brain in catalog.searchResults(path=path):
                    ob = brain.getObject()
                    if ob is None:
                        # Ignore old references to deleted objects.
                        continue
                    s = getattr(ob, '_p_changed', 0)
                    catalog.reindexObject(ob, idxs=['allowedRolesAndUsers'])
                    if s is None: ob._p_deactivate()
                # Reindex the object itself, as the PathIndex only gave us
                # the descendants.
                self.reindexObject(idxs=['allowedRolesAndUsers'])


    #---------------------------------------------------------------#
    # additional methods to help indexing of identities
    #---------------------------------------------------------------#

    def getModelIdentity(self):
        """
            Return Model Object Identity (or None if not available)
        """

        if self.getClassInfo().isPortalObject():
            return None
        else:
            return self.getModel().getIdentity()
    
    #---------------------------------------------------------------#

    def asJavaScript(self, value):
        """
            A helper method to convert the specified value into javascript
            (escape quotes etc)
            
            This is allows us to access the Common conversion methods
            from Page Templates
        """
        
        return asJavaScript(value)
 
    #---------------------------------------------------------------#

    def toHTML(self, value):
        """
            A helper method to convert the specified value into html
            (escape quotes etc)
            
            This is allows us to access the Common conversion methods
            from Page Templates
        """
        
        return toHTML(value)

    #---------------------------------------------------------------#
    # CMF Support:
    #
    # Ensure that the object doesn't have an index_html method.
    # This special value informs ZPublisher to use __call__
    index_html = view
            
            
            
    #---------------------------------------------------------------#
            
    def exportPermissionsAsDOM( self ):
        """ Exports permissions for this object as a DOM tree """
        
        dom = getDOMImplementation().createDocument(None, 'export', None)
        
        security = self.knowledgeengine_securityservice
        permissions = security.getPermissions( self.getIdentity() )
        
        root = dom.createElement( 'permissions' )
               
        xmlize = self.knowledgeengine_tool.xmlize
               
        for permission in permissions:
            node = dom.createElement( 'permission' )
            
            node.setAttribute( 'permissionIdentity',    xmlize( permission.getIdentity() ))
            node.setAttribute( 'securableIdentity',     xmlize( permission.getSecurableIdentity() ))
            node.setAttribute( 'actorIdentity',         xmlize( permission.getActorIdentity()))
            node.setAttribute( 'isGranted',             xmlize( permission.granted() ))
            node.setAttribute( 'instantEffectiveFrom',  xmlize( permission.getInstantEffectiveFrom() ))
            node.setAttribute( 'instantExpiresOn',      xmlize( permission.getInstantExpiresOn() ))
            node.setAttribute( 'creditStrategy',        xmlize( permission.getCreditStrategy() ))
            node.setAttribute( 'assessmentScope',       xmlize( permission.getAssessmentScope() ))
            
            root.appendChild( node )
            
        return root
            
            
    #---------------------------------------------------------------#
    
        
        
            
            
    def exportDOM( self, doExportPermissions=False ):
        """ Performs an DOM Dump of this object, used by exportXML """
               
        # Create a DOM Document
        dom = getDOMImplementation().createDocument(None, 'export', None)
        
        xmlize = self.knowledgeengine_tool.xmlize
        
        # Create the object node
        object = dom.createElement('object')
        object.setAttribute('identity', self.getIdentity())
        object.setAttribute('zopeid',   self.getId())
        object.setAttribute('title',    self.Title())
        object.setAttribute('class',    self.getClassName())
        
        # Create a root element for properties
        propertyRoot = dom.createElement('properties')
        
        # Create an element for each property
        properties = self.getPropertyDefinitions()
        for property in properties:
            
            # Serialize property info
            propertyNode = dom.createElement('property')
            propertyNode.setAttribute('identity', property.getIdentity())
            propertyNode.setAttribute('type',     property.getType())
            value = getattr( self.aq_explicit, property.getIdentity(), None )
            
            
            # HACK - Treat Answer_Lists specially
            # Serialize MCH Question answer lists into individual xml nodes
            if property.getType() == "ANSWER_LIST":
                answerRoot = dom.createElement('answers')
                for answer in value:
                    answerNode = dom.createElement('answer')
                    answerNode.setAttribute('answerID',             answer.getAnswerID() )
                    answerNode.setAttribute('content',              answer.getContent() )
                    answerNode.setAttribute('assessmentContent',    answer.getAssessmentContent() )
                    answerNode.setAttribute('reportContent',        answer.getReportContent() )
                    answerNode.setAttribute('score',                str(answer.getScore()))
                    answerNode.setAttribute('isCorrectValue',       str(answer.isCorrect()))
                    answerNode.setAttribute('option',               answer.getOption())
                    
                    answerRoot.appendChild( answerNode )
                
                propertyNode.appendChild( answerRoot )
            else:
                # All other types can just be stringified.
                #propertyNode.setAttribute('value', xmlize(value))
                propertyNode.appendChild( dom.createTextNode( xmlize( value )))
        
            propertyRoot.appendChild( propertyNode )
            
        object.appendChild( propertyRoot )
        
        if doExportPermissions:
            object.appendChild( self.exportPermissionsAsDOM() )
                        
        # Iterate over the child nodes and recursively append these to the <children> 
        # section of the document                        
        childRoot = dom.createElement('children')
        if self.isContainer():
            for child in self.getChildren():
                childRoot.appendChild( child.exportDOM( doExportPermissions=doExportPermissions ) )
                
            for event in self.getHandledEvents():
                childRoot.appendChild( event.exportDOM( doExportPermissions=doExportPermissions ))
                
            object.appendChild( childRoot )
                        
        # Done
        return object
            
    #---------------------------------------------------------------#            
            
    def exportXML( self, encoding="", doExportPermissions=False, REQUEST=None ):
        """ Performs an XML Dump of this object """
        
        dom = self.exportDOM( doExportPermissions=doExportPermissions )
        xml = dom.toprettyxml()
        
        if encoding == "zuu": # gzip and UUEncode the xml file (Smaller chunk of data to copy-and-paste)
            import uu, zlib, cStringIO 
            
            xmlz   = zlib.compress( xml )
            fin    = cStringIO.StringIO() # "File IN"  for uuencoding
            fout   = cStringIO.StringIO() # "File OUT" for uuencoding
            
            # Put the compressed xml data into the input buffer
            print >> fin, xmlz
            fin.seek(0) # Reset buffer to start
            
            # UUEncode the data and write to the output buffer
            uu.encode( fin, fout, 'base64' )
            
            if REQUEST: REQUEST.RESPONSE.setHeader('content-type','text/plain')
            
            #return fout.getvalue()
            content_type = "text/plain"
            ext          = ".xml.zuu"
            result = fout.getvalue()
        
        else:
            content_type = "text/xml"
            ext          = "xml"
            result = xml
        
        
        if REQUEST:            
            REQUEST.RESPONSE.setHeader('content-type',content_type)
            if not REQUEST.get('inline',None): # Set 'inline=1' in the request to disable forced file downloading
                REQUEST.RESPONSE.setHeader('content-disposition','attachment;filename=%s.%s' % (self.getId(), ext))
                
                
        # Return a nice clean XML File
        return result


    #---------------------------------------------------------------#
    
    def getTypeInfo(self):
        """ Return a Stub TypeInfo object so Plone 2.5 likes us 
            FIXME: KE Really needs to implement proper plone types.
        """
        
        # Try and get a standard plone typeInfo first
        type_info = PloneFolder.getTypeInfo(self)
        
        # If a standard plone typeinfo wasn't found, generate a stub
        if type_info is None:        
            return StubTypeInfo(object=self)
        
        return type_info

    #---------------------------------------------------------------#    

#####################################################################
## Class Initialisation
# (none)
